package com.ycl.blog.util;

import com.ycl.blog.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

/**
 * User: 杨成龙
 * Date: 2019/11/23
 * Time: 2:12 PM
 * Desc: ID生成器
 */
@Slf4j
public final class IdGenerator {
    private static final LocalDateTime START_DATE_TIME = LocalDateTime.parse("2016-01-01 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));


    /**
     * 起始的时间戳, 2016-01-01 00:00:00
     */
    private final static long START_STAMP = getTimestamp(START_DATE_TIME);

    /**
     * 每一部分占用的位数
     */
    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
    private final static long MACHINE_BIT = 10;   //机器标识占用的位数

    /**
     * 每一部分的最大值
     */
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    //    private static long MACHINE_ID;     //机器标识,采用IP地址的后两段,16bit的工作机器id可以支持65536台机器
    private static long SEQUENCE = 0L; //序列号,13序列号支持1毫秒产生8192个自增序列id
    private static long LAST_STAMP = -1L;//上一次时间戳

    private static boolean first = true;
    private static StringRedisTemplate redisTemplate;

    private static AppCommonConfigProperties configProperties;
    private static final String MESSAGE_KEY = "imMessageIdGeneratorKey";

    static {
        String localIp = ImUtils.getLocalIp();
        String[] tempArray = localIp.split("\\.");
        if (tempArray.length != 4) {
            throw new AppException("Local ip is invalid " + localIp);
        }

//        MACHINE_ID = 255 * (Integer.valueOf(tempArray[2]) % 4) + Integer.valueOf(tempArray[3]);
    }

    /**
     * 生成ID，采用snowflake算法，64bit整数，1秒可以生成800万个ID
     * 0-41bit毫秒时间戳-10bit机器ID-12bit序列化
     * 42bit的毫秒时间戳，2000年算起可以支持该算法使用到2068年，10bit的工作机器id可以支持1024台机器，12序列号支持1毫秒产生4096个自增序列id
     *
     * @return 返回Long ID
     */
    public synchronized static Long generateId() {
        long curStamp = getNewStamp();
        if (curStamp < LAST_STAMP) {
            throw new AppException("Clock moved backwards. Refusing to generate id");
        }

        if (curStamp == LAST_STAMP) {
            //相同毫秒内，序列号自增
            SEQUENCE = (SEQUENCE + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (SEQUENCE == 0L) {
                curStamp = getNextMill();
            }
        } else {
            //不同毫秒内，序列号置为0
            SEQUENCE = 0L;
        }

        LAST_STAMP = curStamp;
        return (curStamp - START_STAMP) << TIMESTAMP_LEFT //时间戳部分
                | getMachineId() << MACHINE_LEFT             //机器标识部分
                | SEQUENCE;                             //序列号部分
    }

    /**
     * 通过redis生成严格递增的消息ID
     *
     * @return 递增序列
     */
    @SuppressWarnings("unchecked")
    public static Long generateSequence() {
        if (first) {
            getRedisTemplate().opsForValue().setIfAbsent(MESSAGE_KEY, "100000000000000");
            first = false;
        }
        return getRedisTemplate().opsForValue().increment(MESSAGE_KEY, 1L);
    }

    /**
     * 生成消息ID
     *
     * @return 消息ID
     */
    public static String generateMessageId() {
        return "M" + generateSequence();
    }

    /**
     * 从字符串Message ID中解析出时间戳
     *
     * @param messageId 消息ID
     * @return 时间戳
     */
    public static Long parseMessageId(String messageId) {
        if (StringUtils.isBlank(messageId)) {
            return -1L;
        }

        return Long.valueOf(messageId.substring(1));
    }

    /**
     * 生成通道ID
     *
     * @return 通道ID
     */
    public static String generateChannelId() {
        return "C" + generateId();
    }

    /**
     * 生成角色ID
     *
     * @return 角色ID
     */
    public static String generateActorId() {
        return "AT" + generateId();
    }

    /**
     * 生成用户ID
     *
     * @return 用户ID
     */
    public static String generateUserId() {
        return "U" + generateId();
    }

    private static long getNextMill() {
        long mill = getNewStamp();
        while (mill <= LAST_STAMP) {
            mill = getNewStamp();
        }
        return mill;
    }

    private static long getNewStamp() {
        return System.currentTimeMillis();
    }

    private static long getTimestamp(LocalDateTime dateTime) {
        ZoneId zoneId = ZoneId.systemDefault();
        return dateTime.atZone(zoneId).toInstant().toEpochMilli();
    }

    @SuppressWarnings("unchecked")
    private static StringRedisTemplate getRedisTemplate() {
        if (null == redisTemplate) {
            synchronized (IdGenerator.class) {
                if (null == redisTemplate) {
                    redisTemplate = BeanFactory.getBean(StringRedisTemplate.class);
                }
            }
        }
        return redisTemplate;
    }

    @SuppressWarnings("unchecked")
    private static AppCommonConfigProperties getConfigProperties() {
        if (null == configProperties) {
            synchronized (IdGenerator.class) {
                if (null == configProperties) {
                    try {
                        configProperties = BeanFactory.getBean(AppCommonConfigProperties.class);
                    } catch (Exception e) {
                        configProperties = new AppCommonConfigProperties();
                    }
                }
            }
        }

        return configProperties;
    }

    /**
     * 获取机器码
     *
     * @return 机器编号
     */
    private static long getMachineId() {
        return getConfigProperties().getMachineId();
    }
}
